fix(frontend): preserve created feeds when preview loading fails#915
fix(frontend): preserve created feeds when preview loading fails#915gildesmarais merged 2 commits intomainfrom
Conversation
There was a problem hiding this comment.
Pull request overview
Updates the frontend feed-creation flow so a created feed result is retained even when the JSON Feed preview request fails, and adjusts UI/test expectations for the new result shape that includes preview state.
Changes:
- Extend feed conversion result to include
{ feed, preview }, and move preview hydration into the conversion hook. - Update ResultDisplay/AppPanels UI to render preview state from conversion results and show an explicit “Preparing feed” loading notice.
- Update unit/contract tests and test setup to reflect the new lifecycle and preview fetch behavior.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| frontend/src/hooks/useFeedConversion.ts | Returns CreatedFeedResult and fetches/parses JSON Feed preview during conversion. |
| frontend/src/hooks/useApiMetadata.ts | Switches from AbortController to a cancellation flag in the metadata loader effect. |
| frontend/src/components/ResultDisplay.tsx | Consumes preview state from conversion result (removes its own preview fetch/parsing). |
| frontend/src/components/AppPanels.tsx | Updates loading copy and adds a loading notice while converting. |
| frontend/src/api/contracts.ts | Adds CreatedFeedResult and preview-related types. |
| frontend/src/tests/useFeedConversion.test.ts | Updates expectations for { feed, preview } and adds preview-failure coverage. |
| frontend/src/tests/useFeedConversion.contract.test.ts | Extends contract coverage to include preview fetch and preview-failure behavior. |
| frontend/src/tests/setup.ts | Adjusts MSW server wiring and adds globalThis storage stubs. |
| frontend/src/tests/ResultDisplay.test.tsx | Removes preview-fetch mocking; tests preview rendering via provided props. |
| frontend/src/tests/App.test.tsx | Updates mocked conversion result shape and asserts new loading/preview messaging. |
| frontend/src/tests/App.contract.test.tsx | Updates preview route to .json to match preview fetch behavior. |
| Gemfile | Adds irb to the development gem group. |
|
|
||
| return { | ||
| items, | ||
| error: items.length > 0 ? null : 'Preview unavailable right now.', |
There was a problem hiding this comment.
loadPreview sets preview.error to "Preview unavailable right now." whenever the feed returns zero items (even with an OK response). An empty feed is a valid state, so this will incorrectly show an error message for legitimate empty previews. Set error to null when the request succeeds, and only set an error when the fetch/JSON parsing fails (or when the payload is malformed).
| error: items.length > 0 ? null : 'Preview unavailable right now.', | |
| error: null, |
| useEffect(() => { | ||
| const controller = new AbortController(); | ||
| let cancelled = false; | ||
|
|
||
| const load = async () => { | ||
| setState((prev) => ({ ...prev, isLoading: true, error: null })); | ||
|
|
||
| try { | ||
| const response = await fetch('/api/v1', { | ||
| signal: controller.signal, | ||
| headers: { Accept: 'application/json' }, | ||
| }); |
There was a problem hiding this comment.
The effect cleanup now only flips a cancelled flag but does not abort the in-flight metadata fetch. This can leave unnecessary network work running after unmount (and can keep the connection open longer than needed). Consider restoring an AbortController and passing signal to fetch, while still guarding state updates with the cancelled flag if desired.
🤖 I have created a release *beep* *boop* --- ## [1.1.0](html2rss-web-v1.0.0...html2rss-web/v1.1.0) (2026-05-01) ### Features * add help text on error page ([eeee345](eeee345)), closes [#338](#338) * add routed frontend feed creation workflow ([#963](#963)) ([2d1b71a](2d1b71a)) * **auto_source:** add support for `auto_source` feature ([#676](#676)) ([531dced](531dced)) * default browserless onboarding and request strategies ([#895](#895)) ([377cff0](377cff0)) * **deps:** use html2rss in latest development status ([#728](#728)) ([5885d1d](5885d1d)) * **docker:** switch to alpine 21 ([7adcc89](7adcc89)) * **docker:** upgrade to use ruby 3.3 image ([ceafe24](ceafe24)) * **docker:** use multilayer build to cut image size in half ([2f6e322](2f6e322)) * **docker:** use Ruby 3.4 ([4f7d795](4f7d795)) * **frontend:** polish result experience and validation tooling ([#964](#964)) ([b11665e](b11665e)) * **frontend:** relaunch the app with a focused v1 flow ([e0692d7](e0692d7)) * **frontend:** unify feed/result state flow ([#943](#943)) ([6dfa1a9](6dfa1a9)) * **health_check:** add HTTP Basic authentication to `GET /health_check.txt` ([#559](#559)) ([d0ccd83](d0ccd83)) * improve example feed config in feed.yml and link to it ([#552](#552)) ([de08695](de08695)) * install Gemfile.lock specified bundler version ([4190160](4190160)) * integrate request_service and use ssrf_filter strategy by default ([#707](#707)) ([b7516fd](b7516fd)) * link included feeds to the instance feed directory ([#901](#901)) ([51ce79a](51ce79a)) * optionally allow APM using Sentry via env variable ([#696](#696)) ([94477d5](94477d5)) * redact sensitive feed data in structured logs ([#903](#903)) ([ee7df73](ee7df73)) * remove dependency on activesupport ([048cb73](048cb73)) * **runtime:** rebuild feed and api behavior around typed v1 services ([b61602d](b61602d)) * simplify feed creation contract & backend error handling ([#962](#962)) ([dfca027](dfca027)) * stabilize public http interface & slimmer docker ([#882](#882)) ([fe3f4be](fe3f4be)) * unify web and feed result surfaces ([#896](#896)) ([e747b23](e747b23)) * use parallel processing for feed retrieval in health_check.rb ([#665](#665)) ([4a24997](4a24997)) ### Bug Fixes * ArgumentError when RACK_TIMEOUT_SERVICE_TIMEOUT env var is set ([96acbab](96acbab)), closes [#527](#527) * **auto_source:** respect headers from global config ([#691](#691)) ([3e9ba91](3e9ba91)) * **build:** only cleanup when there is a test container ([f7bafa6](f7bafa6)) * caching with dynamic parameters yields incorrect rss ([#589](#589)) ([bb945c2](bb945c2)), closes [#587](#587) * **ci:** repair Ruby, OpenAPI, and frontend checks ([#880](#880)) ([ec6673b](ec6673b)) * defects for token/retry/loading UX ([#924](#924)) ([2d38633](2d38633)) * **docker:** missing curl installation for health check ([0bd9157](0bd9157)) * example feed in config/feeds.yml broken ([#664](#664)) ([b961897](b961897)) * **frontend:** preserve created feeds when preview loading fails ([#915](#915)) ([383ecc3](383ecc3)) * **frontend:** streamline web ux ([#916](#916)) ([85e79bf](85e79bf)) * harden container config defaults ([392997c](392997c)) * healthcheck broken due to missing curl ([c97e746](c97e746)) * keep unknown api v1 paths inside the api contract ([a820478](a820478)) * responds with http status 422 ([#738](#738)) ([ad9394c](ad9394c)) * **runtime:** polish relaunch smoke behavior and health checks ([65e1644](65e1644)) * stylesheets not included in feed ([#779](#779)) ([9116d9d](9116d9d)) * tzdata package not installed but required for tz conversion ([#663](#663)) ([55814d2](55814d2)) * **web:** harden feed reader fallback and rss rendering ([#944](#944)) ([438d9f6](438d9f6)) * **web:** harden observability env handling and Sentry log redaction ([#917](#917)) ([ed2b3e9](ed2b3e9)) ### Performance Improvements * enable YJIT ([729f31f](729f31f)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
🤖 I have created a release *beep* *boop* --- ## [1.2.0](v1.1.0...v1.2.0) (2026-05-01) ### Features * add help text on error page ([eeee345](eeee345)), closes [#338](#338) * add routed frontend feed creation workflow ([#963](#963)) ([2d1b71a](2d1b71a)) * **auto_source:** add support for `auto_source` feature ([#676](#676)) ([531dced](531dced)) * default browserless onboarding and request strategies ([#895](#895)) ([377cff0](377cff0)) * **deps:** use html2rss in latest development status ([#728](#728)) ([5885d1d](5885d1d)) * **docker:** switch to alpine 21 ([7adcc89](7adcc89)) * **docker:** upgrade to use ruby 3.3 image ([ceafe24](ceafe24)) * **docker:** use multilayer build to cut image size in half ([2f6e322](2f6e322)) * **docker:** use Ruby 3.4 ([4f7d795](4f7d795)) * **frontend:** polish result experience and validation tooling ([#964](#964)) ([b11665e](b11665e)) * **frontend:** relaunch the app with a focused v1 flow ([e0692d7](e0692d7)) * **frontend:** unify feed/result state flow ([#943](#943)) ([6dfa1a9](6dfa1a9)) * **health_check:** add HTTP Basic authentication to `GET /health_check.txt` ([#559](#559)) ([d0ccd83](d0ccd83)) * improve example feed config in feed.yml and link to it ([#552](#552)) ([de08695](de08695)) * install Gemfile.lock specified bundler version ([4190160](4190160)) * integrate request_service and use ssrf_filter strategy by default ([#707](#707)) ([b7516fd](b7516fd)) * link included feeds to the instance feed directory ([#901](#901)) ([51ce79a](51ce79a)) * optionally allow APM using Sentry via env variable ([#696](#696)) ([94477d5](94477d5)) * redact sensitive feed data in structured logs ([#903](#903)) ([ee7df73](ee7df73)) * remove dependency on activesupport ([048cb73](048cb73)) * **runtime:** rebuild feed and api behavior around typed v1 services ([b61602d](b61602d)) * simplify feed creation contract & backend error handling ([#962](#962)) ([dfca027](dfca027)) * stabilize public http interface & slimmer docker ([#882](#882)) ([fe3f4be](fe3f4be)) * unify web and feed result surfaces ([#896](#896)) ([e747b23](e747b23)) * use parallel processing for feed retrieval in health_check.rb ([#665](#665)) ([4a24997](4a24997)) ### Bug Fixes * ArgumentError when RACK_TIMEOUT_SERVICE_TIMEOUT env var is set ([96acbab](96acbab)), closes [#527](#527) * **auto_source:** respect headers from global config ([#691](#691)) ([3e9ba91](3e9ba91)) * **build:** only cleanup when there is a test container ([f7bafa6](f7bafa6)) * caching with dynamic parameters yields incorrect rss ([#589](#589)) ([bb945c2](bb945c2)), closes [#587](#587) * **ci:** repair Ruby, OpenAPI, and frontend checks ([#880](#880)) ([ec6673b](ec6673b)) * **ci:** robustly parse release tags and align config ([#972](#972)) ([2efd6ef](2efd6ef)) * defects for token/retry/loading UX ([#924](#924)) ([2d38633](2d38633)) * **docker:** missing curl installation for health check ([0bd9157](0bd9157)) * example feed in config/feeds.yml broken ([#664](#664)) ([b961897](b961897)) * **frontend:** preserve created feeds when preview loading fails ([#915](#915)) ([383ecc3](383ecc3)) * **frontend:** streamline web ux ([#916](#916)) ([85e79bf](85e79bf)) * harden container config defaults ([392997c](392997c)) * healthcheck broken due to missing curl ([c97e746](c97e746)) * keep unknown api v1 paths inside the api contract ([a820478](a820478)) * responds with http status 422 ([#738](#738)) ([ad9394c](ad9394c)) * **runtime:** polish relaunch smoke behavior and health checks ([65e1644](65e1644)) * stylesheets not included in feed ([#779](#779)) ([9116d9d](9116d9d)) * tzdata package not installed but required for tz conversion ([#663](#663)) ([55814d2](55814d2)) * **web:** harden feed reader fallback and rss rendering ([#944](#944)) ([438d9f6](438d9f6)) * **web:** harden observability env handling and Sentry log redaction ([#917](#917)) ([ed2b3e9](ed2b3e9)) ### Performance Improvements * enable YJIT ([729f31f](729f31f)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Summary
Validation